package localize

import (
	"context"
	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-kratos/kratos/v2/metadata"
	"github.com/go-kratos/kratos/v2/middleware"
	"github.com/go-kratos/kratos/v2/transport"
	"github.com/nicksnyder/go-i18n/v2/i18n"
	"golang.org/x/text/language"
)

type localizeKey struct{}

type Option func(*options)

type options struct {
	bundle    *I18nBundle
	language  string
	headerKey string
	metaKey   string
}

var (
	defaultBundle = i18n.NewBundle(language.English)
)

const clientLanguageHeaderKey = "x-client-language"
const languageMateKey = "x-md-global-language"

func Server(opts ...Option) middleware.Middleware {
	o := &options{
		bundle: &I18nBundle{
			bundle: i18n.NewBundle(language.English),
		},
		language:  "en",
		headerKey: clientLanguageHeaderKey,
		metaKey:   languageMateKey,
	}

	for _, opt := range opts {
		if opt != nil {
			opt(o)
		}
	}
	if o.bundle == nil {
		panic("[I18N] i18n bundle must be set.")
	}
	return func(handler middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
			if tr, ok := transport.FromServerContext(ctx); ok {
				responseHeader := tr.ReplyHeader()
				requestHeader := tr.RequestHeader()
				if md, ok := metadata.FromServerContext(ctx); ok {
					lang := md.Get(o.metaKey)
					if len(lang) == 0 {
						if requestHeader.Get(o.headerKey) != "" {
							lang = requestHeader.Get(o.headerKey)
						} else {
							lang = o.language
						}
					}
					localized := NewLocalized(o.bundle, lang)
					ctx = NewContext(ctx, localized)
					md.Set(o.metaKey, lang)
					ctx = metadata.NewServerContext(ctx, md)
					responseHeader.Set(o.headerKey, lang)
				}
			}
			// call next middleware --替换业务错误模板
			reply, err = handler(ctx, req)
			if err != nil {
				err = TransError(ctx, err)
			}
			return reply, err
		}
	}
}

// NewContext put auth info into context
func NewContext(ctx context.Context, lan *Localized) context.Context {
	return context.WithValue(ctx, localizeKey{}, lan)
}

// FromContext get auth info from context
func FromContext(ctx context.Context) *Localized {
	localized := ctx.Value(localizeKey{})
	if localized == nil {
		return NewLocalized(defaultI18nBundle, "en")
	}
	return localized.(*Localized)
}

// M translate message
func M(ctx context.Context, messageId string) string {
	l := FromContext(ctx)
	return l.M(messageId)
}

// T translate message with data
func T(ctx context.Context, messageId string, data interface{}, pluralCounts ...interface{}) string {
	l := FromContext(ctx)
	return l.T(messageId, data, pluralCounts...)
}

// TransError translate error message
func TransError(ctx context.Context, err error) error {
	l := FromContext(ctx)
	e := errors.FromError(err)
	if e.Reason == "" {
		msg := l.M(e.Message)
		if msg != "" {
			e.Message = l.M(e.Message)
		}
		return e
	}
	if format := l.M(e.Reason); format != "" {
		e.Message = l.T(e.GetReason(), e.GetMetadata())
	}
	return e
}
